package com.errol.generic.file.core.service;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.renjia.properties.UkerServerProperties;
import com.renjia.service.generic.base.service.UnifiedIdService;
import com.renjia.service.generic.file.core.pojo.FileExtension;
import com.renjia.service.generic.file.core.pojo.Filepath;
import com.renjia.service.generic.file.core.pojo.Frequency;
import com.renjia.service.generic.file.core.pojo.TempFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Created by Errol1017 on 2018/5/10.
 *
 * @author zhang xiao bin
 * @version v1.0
 * @date 2020/10/21
 * @since v4.7.6
 */
@Service
class FileServiceImpl implements FileService, FileInnerService {

    private final UnifiedIdService unifiedIdService;
    private final String fileBasePath;
    private final List<FileExtension> imgFormats = Arrays.asList(FileExtension.JPG, FileExtension.JPEG, FileExtension.PNG);

    public FileServiceImpl(UnifiedIdService unifiedIdService, UkerServerProperties ukerServerProperties) {
        this.unifiedIdService = unifiedIdService;
        this.fileBasePath = ukerServerProperties.getFileBasePath();
    }

    @Override
    public TempFile tempFile(@NonNull MultipartFile file, @Nullable FileExtension extension, @Nullable Long maxSize) throws IOException {
        String originalFilename = file.getOriginalFilename();
        if (extension == null) {
            extension = getFileExtension(originalFilename);
        }
        String filePath = getSecondaryFilePath(Filepath.TEMP) + newRandomFilename(extension);
        if (maxSize != null && file.getSize() > maxSize) {
            return new TempFile(null, originalFilename, extension, file.getSize(), null);
        }
        File tempFile = new File(fileBasePath + filePath);
        file.transferTo(tempFile);
        return new TempFile(filePathOut(filePath), originalFilename, extension, tempFile.length(), tempFile);
    }

    @Override
    public boolean isTemp(@NonNull String from) {
        return from.startsWith(Filepath.TEMP.toString().toLowerCase());
    }

    @Override
    public String transfer(@NonNull String from, @NonNull Filepath toDir) throws IOException {
        return transfer(from, toDir, null);
    }

    @Override
    public String transfer(@NonNull String from, @NonNull Filepath toDir, @Nullable String filename) throws IOException {
        return deal(from, toDir, filename, "transfer");
    }

    @Override
    public String copy(@NonNull String from, @NonNull Filepath toDir) throws IOException {
        return copy(from, toDir, null);
    }

    @Override
    public String copy(@NonNull String from, @NonNull Filepath toDir, @Nullable String filename) throws IOException {
        return deal(from, toDir, filename, "copy");
    }

    @Override
    public void delete(@NonNull String from) {
        getFiles(from).forEach(File::delete);
    }

    @Override
    public boolean allExist(@NonNull String from) {
        return getFiles(from).stream().allMatch(File::exists);
    }

    @Override
    public String newFilename(@NonNull Filepath filepath, @NonNull FileExtension fileExtension) {
        return filePathOut(getSecondaryFilePath(filepath) + unifiedIdService.getOne() + fileExtension.getExtension());
    }

    @Override
    public File newFile(@NonNull Filepath filepath, @NonNull FileExtension fileExtension) {
        return new File(newRandomFilePath(filepath, fileExtension));
    }

    @Override
    public File getFile(@NonNull Filepath filepath, @NonNull String filename) {
        return getFile(getSecondaryFilePath(filepath) + filename);
    }

    @Override
    public File getFile(@NonNull String from) {
        return getFiles(from).get(0);
    }

    @Override
    public List<File> getFiles(@NonNull String from) {
        return filePathIn(from).stream().map(s -> new File(fileBasePath + s)).collect(Collectors.toList());
    }

    @Override
    public String writeToJsonFile(@NonNull Object data, @NonNull Filepath toDir) throws IOException {
        String path = getSecondaryFilePath(toDir) + unifiedIdService.getOne() + FileExtension.JSON.getExtension();
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(mapper.getVisibilityChecker()
                .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
                .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withSetterVisibility(JsonAutoDetect.Visibility.NONE));
        mapper.writeValue(new File(fileBasePath + path), data);
        return filePathOut(path);
    }

    @Override
    public String transferFilesBeforeSave(@NonNull String oldFroms, @NonNull String newFroms, @NonNull Filepath toDir) throws IOException {
        if (oldFroms.equals(newFroms)) {
            return oldFroms;
        }
        List<File> newFiles = getFiles(newFroms);
        List<String> filepath = new ArrayList<>();
        for (File newFile : newFiles) {
            if (newFile.exists()) {
                String to = getSecondaryFilePath(toDir) + newRandomFilename(newFile.getName());
                FileUtils.moveFile(newFile, new File(fileBasePath + to));
                filepath.add(to);
            }
        }
        getFiles(oldFroms).stream().filter(File::exists).forEach(File::delete);
        return filePathOut(filepath);
    }

    /**
     * 转移或拷贝文件
     *
     * @param from     源文件路径，可以多个文件，以 , 分隔
     * @param toDir    目标文件夹 Filepath
     * @param filename 目标文件名，含扩展，以 , 分隔，必须与源文件数量一致
     * @param type     操作类型， transfer 或 copy
     * @return 目标文件出口字符串
     * @throws IOException 文件不存在异常
     */
    private String deal(String from, Filepath toDir, String filename, String type) throws IOException {
        List<File> files = getFiles(from);
        List<String> filepath = new ArrayList<>();
        ArrayList<String> names = new ArrayList<>();
        if (filename != null) {
            names.addAll(Arrays.asList(filename.split(",")));
            if (files.size() != names.size()) {
                throw new RuntimeException("定制目标文件名称时，filename 数量必须与 from 一致");
            }
        }
        for (int i = 0; i < files.size(); i++) {
            String to = getSecondaryFilePath(toDir) + (filename == null ? newRandomFilename(from) : names.get(i));
            switch (type) {
                case "transfer":
                    FileUtils.moveFile(files.get(i), new File(fileBasePath + to));
                    break;
                case "copy":
                    FileUtils.copyFile(files.get(i), new File(fileBasePath + to));
                    break;
            }
            filepath.add(to);
        }
        return filePathOut(filepath);
    }

    @Override
    public String getFileBasePath() {
        return fileBasePath;
    }

    @Override
    public FileExtension getFileExtension(String filename) {
        FileExtension fileExtension;
        if (filename != null && (fileExtension = FileExtension.resolve(FilenameUtils.getExtension(filename))) != null) {
            return fileExtension;
        }
        return null;
    }

    @Override
    public String getSecondaryFilePath(@NonNull Filepath filepath) {
        if (filepath.getFrequency() == null) {
            return filepath.getPath();
        } else {
            return filepath.getPath() + filepath.getFrequency().getSecondaryFolderPathOfFilepath();
        }
    }

    @Override
    public String newRandomFilename(@NonNull String filename) {
        return unifiedIdService.getOne() + getFileExtension(filename).getExtension();
    }

    @Override
    public String newRandomFilename(@NonNull FileExtension fileExtension) {
        return unifiedIdService.getOne() + fileExtension.getExtension();
    }

    @Override
    public String newRandomFilePath(@NonNull Filepath filepath, @NonNull FileExtension fileExtension) {
        return fileBasePath + getSecondaryFilePath(filepath) + unifiedIdService.getOne() + fileExtension.getExtension();
    }

    @Override
    public File newRandomFile(@NonNull Filepath filepath, @NonNull FileExtension fileExtension) {
        return new File(newRandomFilePath(filepath, fileExtension));
    }

    @Override
    public String filePathOut(@NonNull List<String> filePath) {
        return filePathOut(String.join(",", filePath));
    }

    @Override
    public String filePathOut(@NonNull String filePath) {
        return filePath.replace(File.separatorChar, '/');
    }

    @Override
    public List<String> filePathIn(@NonNull String filepath) {
        return Arrays.stream(filepath.replace('/', File.separatorChar).split(","))
                .filter(s -> !"".equals(s)).collect(Collectors.toList());
    }

    @Override
    public void createSecondaryFolderOfFilepath(@NonNull Filepath filepath, boolean needPlus) {
        LocalDate localDate = needPlus ? LocalDate.now().plusDays(1) : LocalDate.now();
        Frequency frequency = filepath.getFrequency();
        if (frequency != null) {
            File secondaryDir = new File(fileBasePath + filepath.getPath() + frequency.getSecondaryFolderPathOfFilepath(localDate));
            if (!secondaryDir.exists()) {
                if (!secondaryDir.mkdir()) {
                    throw new RuntimeException("failed creating filepath secondary dir");
                }
            }
        }
    }
}
